package ru.romannep.pc.chopperbattle;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Сетевое соединение. Обертка работы с сокетами. Сделан для наследования и
 * переопределения событий.
 *
 */
public class Connection {
    
    private static int MESSAGES_TO_RESET = 1000;
    private int messagesCount = 0;
    
    String serverAddress;
    int serverPort;
    boolean connected;
    Socket socket;
    ObjectOutputStream out;
    ObjectInputStream in;
    Thread readerThread;

    /**
     * Класс чтения информации из сокета. Выполняется в потоке.
     */
    class SocketReader implements Runnable {

        @Override
        public void run() {
            while (connected) {
                try {
                    NetworkMessage msg = (NetworkMessage) in.readObject();
                    //System.out.println("Con recieving " + msg.s + " com = " + msg.commands);
                    
                    incomingMessage(msg);
                } catch (IOException ex) {
                    connectError(ex);
                } catch (ClassNotFoundException ex) {
                    connectError(ex);
                }

            }
        }
    }

    /**
     * Конструктор по умолчанию. Соединения не устанавливает.
     *
     * @param serverAddress
     * @param serverPort
     */
    public Connection(String serverAddress, int serverPort) {
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;
        connected = false;
    }

    /**
     * Создает объект на основании уже подключенного сокета, вызывать connect не
     * требуется
     *
     * @param s
     */
    public Connection(Socket s) {
        socket = s;
        try {
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
            connected = true;
            onConnect();
            readerThread = new Thread(new Connection.SocketReader());
            readerThread.start();
        } catch (IOException ex) {
            connectError(ex);
        }

    }

    /**
     * Метод установки соединения. В случае удачи, запускает чтение информации
     * из сокета в потоке.
     *
     */
    void connect() {
        if (connected) {
            return;
        }

        try {
            socket = new Socket(serverAddress, serverPort);
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
            connected = true;
            onConnect();
            readerThread = new Thread(new Connection.SocketReader());
            readerThread.start();
        } catch (UnknownHostException ex) {
            connectError(ex);
        } catch (IOException ex) {
            connectError(ex);
        }

    }

    /**
     * Отправка сообщения по сети.
     *
     * @param msg
     */
    void sendMessage(NetworkMessage msg) {
        
        if (connected) {
            try {
                out.writeObject(new NetworkMessage(msg));
                if (++messagesCount > MESSAGES_TO_RESET ) {
                    out.reset();
                }
                //System.out.println("Con sending " + msg.s + " com =" + msg.commands);

            } catch (IOException ex) {
                connectError(ex);
            }
        }
    }

    /**
     * Вызывается при любых исключениях при работе с сокетами.
     *
     * @param ex - исключение
     */
    void connectError(Exception ex) {
        connected = false;
        try {
            if (socket != null) { //ошибка может возникнуть при установке соединения
                socket.close();
            }
        } catch (IOException ex1) {
        }
        onDisconnect(ex);
    }

    /**
     * Завершает соединение.
     *
     */
    void disconnect() {
        if (!connected) {
            return;
        }
        connected = false;
        try {
            socket.close();
        } catch (IOException ex) {
        }
    }

    /**
     * Вызывается при установке соединения. Предназначена для переопределения в
     * классах потомках.
     *
     */
    void onConnect() {
    }

    /**
     * Вызывается при разрыве соединения из-за ошибки. Предназначена для переопределения в
     * классах потомках.
     *
     * @param ex - исключение, вызвавшее разрыв соединения
     */
    void onDisconnect(Exception ex) {
    }

    /**
     * Вызывается при поступлении по сети очередного сообщения Предназначена для
     * переопределения в классах потомках.
     *
     * @param msg
     */
    void incomingMessage(NetworkMessage msg) {
    }
}
